package com.example.seckill.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.seckill.entity.TSeckillGoods;
import com.example.seckill.entity.TSeckillOrder;
import com.example.seckill.entity.dto.SeckillDto;
import com.example.seckill.exception.BusinessException;
import com.example.seckill.mapper.TSeckillGoodsMapper;
import com.example.seckill.mapper.TSeckillOrderMapper;
import com.example.seckill.service.TSeckillGoodsService;
import lombok.AllArgsConstructor;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

import static com.example.seckill.service.impl.StockService.*;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author vicente
 * @since 2021-08-10
 */
@Service
@AllArgsConstructor
public class TSeckillGoodsServiceImpl extends ServiceImpl<TSeckillGoodsMapper, TSeckillGoods> implements TSeckillGoodsService {

    private final TSeckillGoodsMapper seckillGoodsMapper;
    private final TSeckillOrderMapper seckillOrderMapper;
    private final RedissonClient redissonClient;
    private final StockService stockService;
    private final RedisTemplate<String, Object> redisTemplate;
    private final static long expireTime = 600L;
    private final static String GOODS_KEY_PREFIX = "seckill_key:goods:";

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void seckill(SeckillDto seckillDto) {

        //1 判断该商品是否存在
        TSeckillGoods goods = seckillGoodsMapper.selectOne(new LambdaQueryWrapper<TSeckillGoods>()
                .eq(TSeckillGoods::getId, seckillDto.getGoodsId()));
        if (goods == null) {
            throw new BusinessException(202, "商品不存在");
        }

        final String key = "lock:" + seckillDto.getUserId() + "-" + seckillDto.getGoodsId();
        RLock lock = redissonClient.getLock(key);

        try {
            //默认30s的redis过期时间
            lock.lock();
            //2 判断该用户是否已经购买过
            Integer boughtNum = seckillOrderMapper.selectCount(new LambdaQueryWrapper<TSeckillOrder>()
                    .eq(TSeckillOrder::getSeckillGoodsId, seckillDto.getGoodsId())
                    .eq(TSeckillOrder::getUserId, seckillDto.getUserId()));
            if (boughtNum > 0) {
                throw new BusinessException(201, "您已购买过该商品或商品不存在");
            }


            //3 减库存操作
            if (seckillGoodsMapper.updateInventory(seckillDto.getGoodsId()) > 0) {
                //4 插入订单
                TSeckillOrder order = TSeckillOrder.builder()
                        .seckillGoodsId(seckillDto.getGoodsId())
                        .userId(seckillDto.getUserId())
                        .seckillGoodsName(goods.getGoodsName())
                        .seckillGoodsPrice(goods.getPrice()).build();
                int insertNum = seckillOrderMapper.insert(order);
                if (insertNum > 0) {
                    System.out.println("发送mq，告诉用户尽快付款");
                }
            } else {
                throw new BusinessException(203, "存库不足，请抢购其他商品");
            }
        } finally {
            lock.unlock();
        }
    }

    @Override
    public void seckillWithRedisStock(SeckillDto seckillDto) {

        Long goodsId = seckillDto.getGoodsId();

        // 库存ID拼接key
        String redisKey = "seckill_key:stock:" + goodsId;
        // 已购集合key
        String purchasedKey = "seckill_key:purchased:" + goodsId;

        // 扣减库存
        long stock = stockService.stock(redisKey, purchasedKey, seckillDto.getUserId().toString(), expireTime, () -> getStockByDatabase(goodsId));

        if (stock == STOCK_OUTS) {
            throw new BusinessException(203, "存库不足，请抢购其他商品");
        }
        if (stock == GOODS_UNEXISTS) {
            throw new BusinessException(202, "商品不存在");
        }
        if (stock == GOODS_PURCHASED) {
            throw new BusinessException(201, "您已购买过该商品");
        }
        if (stock == DO_NOT_GRAB_LOCK) {
            throw new BusinessException(205, "差一点，请继续");
        }


        //4 插入订单
        CompletableFuture.runAsync(() -> {
            Object o = redisTemplate.opsForValue().get(GOODS_KEY_PREFIX+goodsId);
            assert o != null;
            TSeckillGoods goods = JSON.parseObject(o.toString(), TSeckillGoods.class);

            // 获取库存的地方对商品做了缓存，这里就不需要了
            // if (Objects.isNull(o)) {
            //     RLock lock = redissonClient.getLock("lock:" + goodsKey);
            //     try {
            //         lock.lock();
            //         o = redisTemplate.opsForValue().get(goodsKey);
            //         if (Objects.isNull(o)) {
            //             goods = seckillGoodsMapper.selectById(goodsId);
            //             redisTemplate.opsForValue().set(goodsKey, JSON.toJSONString(goods), expireTime, TimeUnit.SECONDS);
            //         } else {
            //             goods = JSON.parseObject(o.toString(), TSeckillGoods.class);
            //         }
            //     } finally {
            //         if (lock.isLocked()) {
            //             if (lock.isHeldByCurrentThread()) {
            //                 lock.unlock();
            //             }
            //         }
            //     }

            // } else {
            //     goods = JSON.parseObject(o.toString(), TSeckillGoods.class);
            // }

            TSeckillOrder order = TSeckillOrder.builder()
                    .seckillGoodsId(seckillDto.getGoodsId())
                    .userId(seckillDto.getUserId())
                    .seckillGoodsName(goods.getGoodsName())
                    .seckillGoodsPrice(goods.getPrice()).build();

            // 5 发送mq
            // 5.1 死信队列超时支付
            // 5.2 db商品库存扣减
            if (seckillOrderMapper.insert(order) > 0) {
                System.out.println("发送mq，告诉用户尽快付款");
            }
        });
    }

    /**
     * 获取数据库库存
     * @param goodsId 商品id
     * @return 库存
     */
    private Long getStockByDatabase(Long goodsId) {
        TSeckillGoods tSeckillGoods = seckillGoodsMapper.selectById(goodsId);
        if (Objects.isNull(tSeckillGoods)) {
            return null;
        }
        // 商品缓存
        redisTemplate.opsForValue().set(GOODS_KEY_PREFIX+goodsId,JSON.toJSONString(tSeckillGoods),expireTime,TimeUnit.SECONDS);
        return tSeckillGoods.getSeckillNum();
    }
}
